﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace MusicPlayer
{
    public class PlayerStateArgs : EventArgs
    {
        public int SourcePlayer
        {
            get;
            protected set;
        }

        public DriverState State
        {
            get;
            protected set;
        }

        public PlayerStateArgs(int sourcePlayer, DriverState state)
        {
            this.SourcePlayer = sourcePlayer;
            this.State = state;
        }
    }

    public class MediaLocationArgs : EventArgs
    {
        public int SourcePlayer
        {
            get;
            protected set;
        }

        public string MediaLocation
        {
            get;
            protected set;
        }


        public MediaLocationArgs(int sourcePlayer, string mediaLocation)
        {
            this.SourcePlayer = sourcePlayer;
            this.MediaLocation = mediaLocation;
        }
    }

    public class PlayerTimeChanged : EventArgs
    {
        public int SourcePlayer
        {
            get;
            protected set;
        }

        public TimeSpan Time
        {
            get;
            protected set;
        }

        public PlayerTimeChanged(int sourcePlayer, TimeSpan time)
        {
            this.SourcePlayer = sourcePlayer;
            this.Time = time;
        }
    }

    class Core
    {
        class DriverFactory
        {
            private List<DriverInfo> drivers;

            public int DriversCount
            {
                get { return this.drivers.Count; }
            }

            public DriverFactory()
            {
                this.drivers = new List<DriverInfo>();

                this.drivers.Add(DriverDefault.DriverInformation);
                //this.drivers.Add(DriverNull.DriverInformation);
                //this.drivers.Add(DriverNetwork.DriverInformation);
            }

            public Driver CreateDriver(int driverIndex)
            {
                switch (driverIndex)
                {
                    case 0:
                        {
                            return new DriverDefault();
                        }
                    case 1:
                        {
                            throw new ArgumentOutOfRangeException(
                                string.Format("Driver #{0} doesn't exist", driverIndex));
                            //return new DriverNull();
                        }
                    case 2:
                        {
                            throw new ArgumentOutOfRangeException(
                                string.Format("Driver #{0} doesn't exist", driverIndex));
                            //return new DriverNetwork();
                        }
                    default:
                        {
                            throw new ArgumentOutOfRangeException(
                                string.Format("Driver #{0} doesn't exist", driverIndex));
                        }
                }
            }

            public DriverInfo[] GetInstalledDrivers()
            {
                DriverInfo[] driversArray = drivers.ToArray();

                return driversArray;
            }
        }

        public const int PLAYERS_DEFAULT_COUNT = 1;

        Driver[] players;
        //int currentPlayerIndex;

        DriverFactory driverFactory;

        IPlayList currentPlaylist;

        public int PlayersCount
        {
            get;
            private set;
        }

        /// <summary>
        /// Singleton implementation, because the core must be only one instance
        /// </summary>
        /// <returns></returns>
        private static Core instance;

        public event EventHandler<MediaLocationArgs> OnMediaLocationChanged;
        public event EventHandler<PlayerStateArgs> OnStateChanged;
        public event EventHandler<PlayerTimeChanged> OnTimeChanged;

        private Core(int playersCount = 0) 
        {
            //init all class members
            driverFactory = new DriverFactory();

            //init players with the default driver
            if (playersCount <= 0)
            {
                this.PlayersCount = PLAYERS_DEFAULT_COUNT;
            }
            else
            {
                this.PlayersCount = playersCount;
            }

            players = new Driver[this.PlayersCount];
            for (int index = 0; index < this.PlayersCount; index++)
            {
                players[index] = driverFactory.CreateDriver(0);
                players[index].OnMediaEnd += Core_OnMediaEnd;
                players[index].OnMediaLocationChange += Core_OnMediaLocationChange;
                players[index].OnStateChange += Core_OnStateChange;
                players[index].OnTimeChange += Core_OnTimeChange;
            }

            //currentPlayerIndex = 0;
        }

        void Core_OnTimeChange(object sender, DriverTimeArgs e)
        {
            for (int index = 0; index < players.Length; index++)
            {
                if (players[index].Equals(sender))
                {
                    if (OnTimeChanged != null)
                    {
                        OnTimeChanged(this, new PlayerTimeChanged(index, e.Time));
                    }

                    break;
                }
            }
        }

        void Core_OnStateChange(object sender, DriverStateArgs e)
        {
            for (int index = 0; index < players.Length; index++)
            {
                if (players[index].Equals(sender))
                {
                    if (OnStateChanged != null)
                    {
                        OnStateChanged(this, new PlayerStateArgs(index, e.State));
                    }

                    break;
                }
            }
        }

        void Core_OnMediaLocationChange(object sender, DriverMediaLocationArgs e)
        {
            for (int index = 0; index < players.Length; index++)
            {
                if (players[index].Equals(sender))
                {
                    if (OnMediaLocationChanged != null)
                    {
                        OnMediaLocationChanged(this, new MediaLocationArgs(index, e.Location));
                    }

                    break;
                }
            }
        }

        void Core_OnMediaEnd(object sender, EventArgs e)
        {
            foreach (var player in players)
            {
                if (player.Equals(sender))
                {
                    string nextSongLocation = currentPlaylist.GetNextSongToPlay();

                    player.Play(nextSongLocation);
                }
            }
        }

        public static Core Instance(int playersCount = 0)
        {
            if (instance == null)
            {
                {
                    if (instance == null)
                        instance = new Core(playersCount);
                }
            }
            return instance;
        }

        public void AddInputHandler(ICoreCommands handler)
        {
            //Subscribe to all events we need to handle
            handler.OnPlay += handler_OnPlay;
            handler.OnPause += handler_OnPause;
            handler.OnStop += handler_OnStop;

            handler.OnPrevious += handler_OnPrevious;
            handler.OnNext += handler_OnNext;

            handler.OnVolumeChange += handler_OnVolumeChange;
            handler.OnMute += handler_OnMute;

            //handler.OnPlayerChange += handler_OnPlayerChange;
            handler.OnDriverChange += handler_OnDriverChange;
        }

        void handler_OnMute(object sender, BaseCoreArgs e)
        {
            if (e != null)
            {
                CheckPlayerIndex(e.TargetPlayer);
                players[e.TargetPlayer].Mute();
            }
            else
            {
                players[0].Mute();
            }
        }

        void handler_OnDriverChange(object sender, DriverChangeArgs e)
        {
            if (e.NewDriver < 0 || e.NewDriver >= driverFactory.DriversCount)
            {
                throw new ArgumentOutOfRangeException(
                                string.Format("Driver #{0} doesn't exist", e.NewDriver));
            }

            CheckPlayerIndex(e.TargetPlayer);

            players[e.TargetPlayer] = driverFactory.CreateDriver(e.NewDriver);
        }

        //void handler_OnPlayerChange(object sender, PlayerChangeArgs e)
        //{
        //    if (e.NewPlayer < 0 || e.NewPlayer >= this.PlayersCount)
        //    {
        //        throw new ArgumentOutOfRangeException(
        //            string.Format("Player {0} doesn't exist", e.NewPlayer));
        //    }

        //    currentPlayerIndex = e.NewPlayer;
        //}

        void handler_OnVolumeChange(object sender, VolumeChangeEventArgs e)
        {
            CheckPlayerIndex(e.TargetPlayer);

            players[e.TargetPlayer].SetVolume(e.NewVolume);
        }

        void handler_OnNext(object sender, BaseCoreArgs e)
        {
            string nextSongLocation = currentPlaylist.GetNextSongToPlay();
            int targetPlayer = 0;

            if (e != null)
            {
                targetPlayer = e.TargetPlayer;
            }

            CheckPlayerIndex(targetPlayer);

            this.PlayMedia(targetPlayer, nextSongLocation);
        }

        void handler_OnPrevious(object sender, BaseCoreArgs e)
        {
            string prevSongLocation = currentPlaylist.GetPrevSongToPlay();
            int targetPlayer = 0;

            if (e != null)
            {
                targetPlayer = e.TargetPlayer;
            }

            CheckPlayerIndex(targetPlayer);

            this.PlayMedia(targetPlayer, prevSongLocation);
        }

        void handler_OnStop(object sender, BaseCoreArgs e)
        {
            int targetPlayer = 0;

            if (e != null)
            {
                targetPlayer = e.TargetPlayer;
            }

            CheckPlayerIndex(targetPlayer);

            this.players[e.TargetPlayer].Stop();         
        }

        void handler_OnPause(object sender, BaseCoreArgs e)
        {
            int targetPlayer = 0;

            if (e != null)
            {
                targetPlayer = e.TargetPlayer; 
            }

            CheckPlayerIndex(targetPlayer);

            this.players[targetPlayer].Pause();
        }

        private void handler_OnPlay(object sender, BaseCoreArgs e)
        {
            string currentSongLocation = currentPlaylist.GetCurrentSongToPlay();
            int targetPlayer = 0;

            if (e != null)
            {
                targetPlayer = e.TargetPlayer; 
            }

            CheckPlayerIndex(targetPlayer);

            this.PlayMedia(targetPlayer, currentSongLocation);
        }

        public void SetPlaylist(IPlayList playlist)
        {
            this.currentPlaylist = playlist;
        }

        public DriverInfo[] GetInstalledDrivers()
        {
            return driverFactory.GetInstalledDrivers();
        }

        private void CheckPlayerIndex(int playerIndex)
        {
            if (playerIndex < 0 || playerIndex >= players.Length)
            {
                throw new ArgumentOutOfRangeException(
                    string.Format("Player #{0} doesn't exist", playerIndex));
            }
        }

        private void PlayMedia(int targetPlayer, string mediaLocation)
        {
            try
            {
                players[targetPlayer].Play(mediaLocation);
            }
            catch (DriverMediaLoadingException dmle)
            {
                throw new CoreMediaFailureException(dmle.Message, targetPlayer, dmle);
            }
        }
    }
}
